package ch.hearc.infrarouge.tools;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import ch.hearc.infrarouge.database.DBCache;
import ch.hearc.infrarouge.models.Forum;
import ch.hearc.infrarouge.models.ForumThread;
import ch.hearc.infrarouge.models.Reply;
import ch.hearc.infrarouge.models.ThreadMessage;
import ch.hearc.infrarouge.models.User;

public class ParserTools {

	public static void parse(String url) throws IOException {
		Document doc = ParserTools.getDocumentFromURL(url);

		Forum forum = ParserTools.parseForum(doc);
		List<ForumThread> forumThreads = ParserTools.parseForumThreads(doc,
				forum);
		ParserTools.parseAllThreads(forumThreads);
	}

	/**
	 * Returns the Document from the given URL
	 * 
	 * @param url
	 * @return
	 */
	public static Document getDocumentFromURL(String url) {
		Document document = null;
		try {
			document = Jsoup.parse(new URL(url).openStream(), "UTF-8", url);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return document;
	}

	/**
	 * Parses the forum contained by the given document.
	 * 
	 * @param doc
	 * @return
	 */
	public static Forum parseForum(Document doc) {
		Elements forumTitleElement = doc
				.select("div.forum-intro h1.forum-title a");
		Elements forumDescriptionElement = doc
				.select("div.forum-intro div.main-img + p");

		String title = forumTitleElement.text().trim();
		String description = forumDescriptionElement.text().trim();

		// page with video
		if (title.isEmpty()) {
			forumTitleElement = doc
					.select("div.forum-intro h1.forum-title-with-video a");
			title = forumTitleElement.text().trim();
		}

		// page with video
		if (description.isEmpty()) {
			forumDescriptionElement = doc
					.select("div.forum-intro div#large-player + p");
			description = forumDescriptionElement.text().trim();
		}

		Forum forum = new Forum(title, description);
		DebugTools.debugln(forum);

		// add forum to DB cache
		DBCache.getInstance().getForums().add(forum);

		return forum;
	}

	/**
	 * Parses all the forum threads for the given document and forum.
	 * 
	 * @param doc
	 * @param forum
	 * @return
	 */
	public static List<ForumThread> parseForumThreads(Document doc, Forum forum) {
		Elements forumThreadsTitlesElement = doc
				.select("div#ir-col-main div.forum-post strong.title");
		Elements forumThreadsLinksElement = doc
				.select("div#ir-col-main div.forum-post a.threadlink");
		Elements forumThreadsAuthorElement = doc
				.select("div#ir-col-main div.forum-post span.member strong");

		// thread links
		List<String> threadLinks = new LinkedList<>();
		for (Element element : forumThreadsLinksElement) {
			threadLinks.add(element.attr("href"));
		}

		// thread titles
		List<String> threadTitles = new LinkedList<>();
		for (Element element : forumThreadsTitlesElement) {
			threadTitles.add(element.text().trim());
		}

		// thread author
		List<String> threadAuthors = new LinkedList<>();
		for (Element element : forumThreadsAuthorElement) {
			threadAuthors.add(element.text().trim());
		}

		List<ForumThread> forumThreads = new LinkedList<>();
		for (int i = 0; i < threadTitles.size(); i++) {
			String title = threadTitles.get(i);
			String url = threadLinks.get(i);

			String username = threadAuthors.get(i);
			User user = DBCache.getInstance().findUser(username, -1);
			ForumThread thread = new ForumThread(title, url, forum, user);
			forumThreads.add(thread);
		}

		// save all forum threads to DB cache
		DBCache.getInstance().getForumThreads().addAll(forumThreads);

		return forumThreads;
	}

	/**
	 * Parses all threads from the given list.
	 * 
	 * @param forumThreads
	 * @throws IOException
	 */
	public static List<Reply> parseAllThreads(List<ForumThread> forumThreads)
			throws IOException {
		LinkedList<Reply> allReplies = new LinkedList<Reply>();

		for (ForumThread forumThread : forumThreads) {
//			DebugTools.debugln("Thread: " + forumThread.getTitle());
			Document doc = getDocumentFromURL(forumThread.getUrl());
			List<Reply> forumThreadReplies = parseThreadMessages(doc,
					forumThread);
			allReplies.addAll(forumThreadReplies);
		}

		// save replies to DB cache
		DBCache.getInstance().getReplies().addAll(allReplies);

		return allReplies;
	}

	/**
	 * Returns the number of messages in this thread.
	 * 
	 * @param doc
	 * @param forumThread
	 * @return
	 */
	public static int getNumberOfMessagesInThread(Document doc,
			ForumThread forumThread) {
		return doc.select("div.forum-post.open").size();
	}

	/**
	 * Parses all the thread messages from the given document.
	 * 
	 * @param doc
	 */
	public static List<Reply> parseThreadMessages(Document doc,
			ForumThread forumThread) {
		Elements forumMessagesElement = doc.select("div.forum-post.open");

		List<Reply> threadReplies = new LinkedList<Reply>();

		List<ThreadMessage> threadMessages = new LinkedList<>();

		boolean isFirstPost = true;
		for (Element element : forumMessagesElement) {
			Elements usernameElement = element
					.select("span.threadlink a.member strong");
			Elements messageTextElement = element.select("div.text");
			Elements messageIdElement = element.select("a.permalink");
			Elements dateElement = element.select("span.date");

			String message = messageTextElement.text().trim();

			String userLink = element.select("a.member").attr("href");
			userLink = userLink.split("-")[1];

			long userId = Long.parseLong(userLink);
			String username = usernameElement.text().trim();

			User author = DBCache.getInstance().findUser(username, userId);
			author.setId(userId);

			String messageId = messageIdElement.text().trim();
			messageId = messageId.substring(1, messageId.length());

			long id = Long.parseLong(messageId);

			// collect the time and date the message was posted
			String postTime = dateElement.text().trim();
			long timestamp = parseTime(postTime);

			ThreadMessage threadMessage = new ThreadMessage(message, author,
					forumThread, id, timestamp);
			threadMessages.add(threadMessage);

			List<Reply> replies = threadMessage.extractReplies(isFirstPost);
			isFirstPost = false;
			threadReplies.addAll(replies);
		}

		// add thread messages to DB cache
		DBCache.getInstance().getThreadMessages().addAll(threadMessages);

		return threadReplies;
	}

	private static long parseTime(String postTime) {
		// example: "Le 20 février 2013 à 8:36"

		String dateOnly = postTime.replace("Le ", "");
		dateOnly = dateOnly.replace(" à", "");
		dateOnly = dateOnly.replace(":", " ");

		String[] dateElements = dateOnly.split(" ");
		int day = Integer.parseInt(dateElements[0]);
		String frenchMonth = dateElements[1];
		int month = frenchMonthToMonth(frenchMonth);
		int year = Integer.parseInt(dateElements[2]);
		int hour = Integer.parseInt(dateElements[3]);
		int minute = Integer.parseInt(dateElements[4]);

		Calendar calendar = new GregorianCalendar();
		calendar.set(Calendar.DAY_OF_MONTH, day);
		calendar.set(Calendar.MONTH, month);
		calendar.set(Calendar.YEAR, year);
		calendar.set(Calendar.HOUR, hour);
		calendar.set(Calendar.MINUTE, minute);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);

		long timestamp = calendar.getTimeInMillis();
		return timestamp;
	}

	private static int frenchMonthToMonth(String frenchMonth) {
		switch (frenchMonth.toLowerCase()) {
		case "janvier":
			return 0;
		case "février":
			return 1;
		case "mars":
			return 2;
		case "avril":
			return 3;
		case "mai":
			return 4;
		case "juin":
			return 5;
		case "juillet":
			return 6;
		case "août":
			return 7;
		case "septembre":
			return 8;
		case "octobre":
			return 9;
		case "novembre":
			return 10;
		case "décembre":
			return 11;
		default:
			try {
				throw new Exception("Invalid month: " + frenchMonth);
			} catch (Exception e) {
				e.printStackTrace();
			}
			return -1;
		}
	}

}
